# Copyright 2020 by Kurt Rathjen. All Rights Reserved.
#
# This library is free software: you can redistribute it and/or modify it 
# under the terms of the GNU Lesser General Public License as published by 
# the Free Software Foundation, either version 3 of the License, or 
# (at your option) any later version. This library is distributed in the 
# hope that it will be useful, but WITHOUT ANY WARRANTY; without even the 
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
# See the GNU Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public
# License along with this library. If not, see <http://www.gnu.org/licenses/>.

import logging

import FXmutils
from collections import OrderedDict

__all__ = [
	"matchNames",
	"groupObjects",
]


logger = logging.getLogger(__name__)


def rotateSequence(seq, current):
	"""
	:type seq:
	:type current:
	:rtype:
	"""
	n = len(seq)
	for i in range(n):
		yield seq[(i + current) % n]


def groupObjects(objects):
	"""
	:type objects:
	:rtype:
	"""
	results = OrderedDict()
	for name in objects:
		node = FXmutils.Node(name)
		results.setdefault(node.namespace(), [])
		results[node.namespace()].append(name)
	results = OrderedDict(sorted(results.items(), key=lambda t: (t[0].count(":"), len(t[0]))))
	return results


def indexObjects(objects):
	"""
	:type objects: list[str]
	:rtype: dict
	"""
	result = {}
	if objects:
		for name in objects:
			node = FXmutils.Node(name)
			result.setdefault(node.shortname(), [])
			result[node.shortname()].append(node)
	return result


def matchInIndex(node, index):
	"""
	:type node: FXmutils.Node
	:type index: dict[list[FXmutils.Node]]
	:rtype: Node
	"""
	result = None
	if node.shortname() in index:
		nodes = index[node.shortname()]
		if nodes:
			for n in nodes:
				if node.name().endswith(n.name()) or n.name().endswith(node.name()):
					result = n
					break
		if result is not None:
			index[node.shortname()].remove(result)

	return result


def matchNames(srcObjects, dstObjects=None, dstNamespaces=None, search=None, replace=None):
	"""
	:type srcObjects: list[str]
	:type dstObjects: list[str]
	:type dstNamespaces: list[str]
	:rtype: list[(FXmutils.Node, FXmutils.Node)]
	"""
	results = []
	if dstObjects is None:
		dstObjects = []

	srcGroup = groupObjects(srcObjects)
	srcNamespaces = srcGroup.keys()

	if not dstObjects and not dstNamespaces:  # and not selection:
		dstNamespaces = srcNamespaces

	if not dstNamespaces and dstObjects:
		dstGroup = groupObjects(dstObjects)
		dstNamespaces = dstGroup.keys()

	dstIndex = indexObjects(dstObjects)
	# DESTINATION NAMESPACES NOT IN SOURCE OBJECTS
	dstNamespaces2 = [x for x in dstNamespaces if x not in srcNamespaces]

	# DESTINATION NAMESPACES IN SOURCE OBJECTS
	dstNamespaces1 = [x for x in dstNamespaces if x not in dstNamespaces2]

	# CACHE DESTINATION OBJECTS WITH NAMESPACES IN SOURCE OBJECTS
	usedNamespaces = []
	notUsedNamespaces = []

	# FIRST LOOP THROUGH ALL DESTINATION NAMESPACES IN SOURCE OBJECTS
	for srcNamespace in srcNamespaces:
		if srcNamespace in dstNamespaces1:
			usedNamespaces.append(srcNamespace)
			for name in srcGroup[srcNamespace]:

				srcNode = FXmutils.Node(name)

				if search is not None and replace is not None:
					# Using the mirror table which supports * style replacing
					name = FXmutils.MirrorTable.replace(name, search, replace)

				dstNode = FXmutils.Node(name)

				if dstObjects:
					dstNode = matchInIndex(dstNode, dstIndex)
				if dstNode:
					results.append((srcNode, dstNode))
					yield (srcNode, dstNode)
				else:
					logger.debug("Cannot find matching destination object for %s" % srcNode.name())
		else:
			notUsedNamespaces.append(srcNamespace)

	# SECOND LOOP THROUGH ALL OTHER DESTINATION NAMESPACES
	srcNamespaces = notUsedNamespaces
	srcNamespaces.extend(usedNamespaces)
	_index = 0
	for dstNamespace in dstNamespaces2:
		match = False
		i = _index
		for srcNamespace in rotateSequence(srcNamespaces, _index):
			if match:
				_index = i
				break
			i += 1
			for name in srcGroup[srcNamespace]:
				srcNode = FXmutils.Node(name)
				dstNode = FXmutils.Node(name)
				dstNode.setNamespace(dstNamespace)

				if dstObjects:
					dstNode = matchInIndex(dstNode, dstIndex)
				elif dstNamespaces:
					pass

				if dstNode:
					match = True
					results.append((srcNode, dstNode))
					yield (srcNode, dstNode)
				else:
					logger.debug("Cannot find matching destination object for %s" % srcNode.name())

	if logger.parent.level == logging.DEBUG or logger.level == logging.DEBUG:
		for dstNodes in dstIndex.values():
			for dstNode in dstNodes:
				logger.debug("Cannot find matching source object for %s" % dstNode.name())
